<!DOCTYPE HTML>
<html lang="zh-Hans" class="light" dir="ltr">
    <head>
        <!-- Book generated using mdBook -->
        <meta charset="UTF-8">
        <title>TypeScript 5.2 - TypeScript 使用指南手册</title>


        <!-- Custom HTML head -->
        
        <meta name="description" content="TypeScript Handbook 中文翻译。">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="theme-color" content="#ffffff">

        <link rel="icon" href="../../favicon.svg">
        <link rel="shortcut icon" href="../../favicon.png">
        <link rel="stylesheet" href="../../css/variables.css">
        <link rel="stylesheet" href="../../css/general.css">
        <link rel="stylesheet" href="../../css/chrome.css">
        <link rel="stylesheet" href="../../css/print.css" media="print">

        <!-- Fonts -->
        <link rel="stylesheet" href="../../FontAwesome/css/font-awesome.css">
        <link rel="stylesheet" href="../../fonts/fonts.css">

        <!-- Highlight.js Stylesheets -->
        <link rel="stylesheet" href="../../highlight.css">
        <link rel="stylesheet" href="../../tomorrow-night.css">
        <link rel="stylesheet" href="../../ayu-highlight.css">

        <!-- Custom theme stylesheets -->

    </head>
    <body class="sidebar-visible no-js">
    <div id="body-container">
        <!-- Provide site root to javascript -->
        <script>
            var path_to_root = "../../";
            var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
        </script>

        <!-- Work around some values being stored in localStorage wrapped in quotes -->
        <script>
            try {
                var theme = localStorage.getItem('mdbook-theme');
                var sidebar = localStorage.getItem('mdbook-sidebar');

                if (theme.startsWith('"') && theme.endsWith('"')) {
                    localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
                }

                if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
                    localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
                }
            } catch (e) { }
        </script>

        <!-- Set the theme before any content is loaded, prevents flash -->
        <script>
            var theme;
            try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
            if (theme === null || theme === undefined) { theme = default_theme; }
            var html = document.querySelector('html');
            html.classList.remove('light')
            html.classList.add(theme);
            var body = document.querySelector('body');
            body.classList.remove('no-js')
            body.classList.add('js');
        </script>

        <input type="checkbox" id="sidebar-toggle-anchor" class="hidden">

        <!-- Hide / unhide sidebar before it is displayed -->
        <script>
            var body = document.querySelector('body');
            var sidebar = null;
            var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
            if (document.body.clientWidth >= 1080) {
                try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
                sidebar = sidebar || 'visible';
            } else {
                sidebar = 'hidden';
            }
            sidebar_toggle.checked = sidebar === 'visible';
            body.classList.remove('sidebar-visible');
            body.classList.add("sidebar-" + sidebar);
        </script>

        <nav id="sidebar" class="sidebar" aria-label="Table of contents">
            <div class="sidebar-scrollbox">
                <ol class="chapter"><li class="chapter-item expanded affix "><a href="../../PREFACE.html">前言</a></li><li class="chapter-item expanded affix "><li class="part-title">快速上手</li><li class="chapter-item expanded "><a href="../../zh/tutorials/index.html"><strong aria-hidden="true">1.</strong> 快速上手</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../zh/tutorials/typescript-in-5-minutes.html"><strong aria-hidden="true">1.1.</strong> 5 分钟了解 TypeScript</a></li><li class="chapter-item expanded "><a href="../../zh/tutorials/asp.net-core.html"><strong aria-hidden="true">1.2.</strong> ASP.NET Core</a></li><li class="chapter-item expanded "><a href="../../zh/tutorials/asp.net-4.html"><strong aria-hidden="true">1.3.</strong> ASP.NET 4</a></li><li class="chapter-item expanded "><a href="../../zh/tutorials/gulp.html"><strong aria-hidden="true">1.4.</strong> Gulp</a></li><li class="chapter-item expanded "><a href="../../zh/tutorials/knockout.html"><strong aria-hidden="true">1.5.</strong> Knockout.js</a></li><li class="chapter-item expanded "><a href="../../zh/tutorials/react-and-webpack.html"><strong aria-hidden="true">1.6.</strong> React 与 webpack</a></li><li class="chapter-item expanded "><a href="../../zh/tutorials/react.html"><strong aria-hidden="true">1.7.</strong> React</a></li><li class="chapter-item expanded "><a href="../../zh/tutorials/angular-2.html"><strong aria-hidden="true">1.8.</strong> Angular 2</a></li><li class="chapter-item expanded "><a href="../../zh/tutorials/migrating-from-javascript.html"><strong aria-hidden="true">1.9.</strong> 从 JavaScript 迁移到 TypeScript</a></li></ol></li><li class="chapter-item expanded "><li class="part-title">手册</li><li class="chapter-item expanded "><a href="../../zh/handbook/index.html"><strong aria-hidden="true">2.</strong> 手册</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../zh/handbook/basic-types.html"><strong aria-hidden="true">2.1.</strong> 基础类型</a></li><li class="chapter-item expanded "><a href="../../zh/handbook/interfaces.html"><strong aria-hidden="true">2.2.</strong> 接口</a></li><li class="chapter-item expanded "><a href="../../zh/handbook/functions.html"><strong aria-hidden="true">2.3.</strong> 函数</a></li><li class="chapter-item expanded "><a href="../../zh/handbook/literal-types.html"><strong aria-hidden="true">2.4.</strong> 字面量类型</a></li><li class="chapter-item expanded "><a href="../../zh/handbook/unions-and-intersections.html"><strong aria-hidden="true">2.5.</strong> 联合类型和交叉类型</a></li><li class="chapter-item expanded "><a href="../../zh/handbook/classes.html"><strong aria-hidden="true">2.6.</strong> 类</a></li><li class="chapter-item expanded "><a href="../../zh/handbook/enums.html"><strong aria-hidden="true">2.7.</strong> 枚举</a></li><li class="chapter-item expanded "><a href="../../zh/handbook/generics.html"><strong aria-hidden="true">2.8.</strong> 泛型</a></li></ol></li><li class="chapter-item expanded "><li class="part-title">手册（进阶）</li><li class="chapter-item expanded "><a href="../../zh/reference/index.html"><strong aria-hidden="true">3.</strong> 手册（进阶）</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../zh/reference/advanced-types.html"><strong aria-hidden="true">3.1.</strong> 高级类型</a></li><li class="chapter-item expanded "><a href="../../zh/reference/utility-types.html"><strong aria-hidden="true">3.2.</strong> 实用工具类型</a></li><li class="chapter-item expanded "><a href="../../zh/reference/decorators.html"><strong aria-hidden="true">3.3.</strong> Decorators</a></li><li class="chapter-item expanded "><a href="../../zh/reference/declaration-merging.html"><strong aria-hidden="true">3.4.</strong> 声明合并</a></li><li class="chapter-item expanded "><a href="../../zh/reference/iterators-and-generators.html"><strong aria-hidden="true">3.5.</strong> Iterators 和 Generators</a></li><li class="chapter-item expanded "><a href="../../zh/reference/jsx.html"><strong aria-hidden="true">3.6.</strong> JSX</a></li><li class="chapter-item expanded "><a href="../../zh/reference/mixins.html"><strong aria-hidden="true">3.7.</strong> 混入</a></li><li class="chapter-item expanded "><a href="../../zh/reference/modules.html"><strong aria-hidden="true">3.8.</strong> 模块</a></li><li class="chapter-item expanded "><a href="../../zh/reference/module-resolution.html"><strong aria-hidden="true">3.9.</strong> 模块解析</a></li><li class="chapter-item expanded "><a href="../../zh/reference/namespaces.html"><strong aria-hidden="true">3.10.</strong> 命名空间</a></li><li class="chapter-item expanded "><a href="../../zh/reference/namespaces-and-modules.html"><strong aria-hidden="true">3.11.</strong> 命名空间和模块</a></li><li class="chapter-item expanded "><a href="../../zh/reference/symbols.html"><strong aria-hidden="true">3.12.</strong> Symbols</a></li><li class="chapter-item expanded "><a href="../../zh/reference/triple-slash-directives.html"><strong aria-hidden="true">3.13.</strong> 三斜线指令</a></li><li class="chapter-item expanded "><a href="../../zh/reference/type-compatibility.html"><strong aria-hidden="true">3.14.</strong> 类型兼容性</a></li><li class="chapter-item expanded "><a href="../../zh/reference/type-inference.html"><strong aria-hidden="true">3.15.</strong> 类型推论</a></li><li class="chapter-item expanded "><a href="../../zh/reference/variable-declarations.html"><strong aria-hidden="true">3.16.</strong> 变量声明</a></li></ol></li><li class="chapter-item expanded "><li class="part-title">手册（v2）</li><li class="chapter-item expanded "><a href="../../zh/handbook-v2/index.html"><strong aria-hidden="true">4.</strong> 手册（v2）</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../zh/handbook-v2/type-manipulation/template-literal-types.html"><strong aria-hidden="true">4.1.</strong> 模版字面量类型</a></li></ol></li><li class="chapter-item expanded "><li class="part-title">TypeScript 声明文件（.d.ts）</li><li class="chapter-item expanded "><a href="../../zh/declaration-files/index.html"><strong aria-hidden="true">5.</strong> 如何书写声明文件</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../zh/declaration-files/introduction.html"><strong aria-hidden="true">5.1.</strong> 介绍</a></li><li class="chapter-item expanded "><a href="../../zh/declaration-files/by-example.html"><strong aria-hidden="true">5.2.</strong> 举例</a></li><li class="chapter-item expanded "><a href="../../zh/declaration-files/library-structures.html"><strong aria-hidden="true">5.3.</strong> 库结构</a></li><li class="chapter-item expanded "><a href="../../zh/declaration-files/templates.html"><strong aria-hidden="true">5.4.</strong> 模板</a></li><li class="chapter-item expanded "><a href="../../zh/declaration-files/do-s-and-don-ts.html"><strong aria-hidden="true">5.5.</strong> 最佳实践</a></li><li class="chapter-item expanded "><a href="../../zh/declaration-files/deep-dive.html"><strong aria-hidden="true">5.6.</strong> 深入</a></li><li class="chapter-item expanded "><a href="../../zh/declaration-files/publishing.html"><strong aria-hidden="true">5.7.</strong> 发布</a></li><li class="chapter-item expanded "><a href="../../zh/declaration-files/consumption.html"><strong aria-hidden="true">5.8.</strong> 使用</a></li></ol></li><li class="chapter-item expanded "><li class="part-title">TypeScript for JavaScript</li><li class="chapter-item expanded "><a href="../../zh/javascript/type-checking-javascript-files.html"><strong aria-hidden="true">6.</strong> JavaScript 文件里的类型检查</a></li><li class="chapter-item expanded affix "><li class="part-title">工程配置</li><li class="chapter-item expanded "><a href="../../zh/project-config/index.html"><strong aria-hidden="true">7.</strong> 工程配置</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../zh/project-config/tsconfig.json.html"><strong aria-hidden="true">7.1.</strong> tsconfig.json</a></li><li class="chapter-item expanded "><a href="../../zh/project-config/project-references.html"><strong aria-hidden="true">7.2.</strong> 工程引用</a></li><li class="chapter-item expanded "><a href="../../zh/project-config/typings-for-npm-packages.html"><strong aria-hidden="true">7.3.</strong> NPM 包的类型</a></li><li class="chapter-item expanded "><a href="../../zh/project-config/compiler-options.html"><strong aria-hidden="true">7.4.</strong> 编译选项</a></li><li class="chapter-item expanded "><a href="../../zh/project-config/configuring-watch.html"><strong aria-hidden="true">7.5.</strong> 配置 Watch</a></li><li class="chapter-item expanded "><a href="../../zh/project-config/compiler-options-in-msbuild.html"><strong aria-hidden="true">7.6.</strong> 在 MSBuild 里使用编译选项</a></li><li class="chapter-item expanded "><a href="../../zh/project-config/integrating-with-build-tools.html"><strong aria-hidden="true">7.7.</strong> 与其它构建工具整合</a></li><li class="chapter-item expanded "><a href="../../zh/project-config/nightly-builds.html"><strong aria-hidden="true">7.8.</strong> 使用 TypeScript 的每日构建版本</a></li></ol></li><li class="chapter-item expanded "><li class="part-title">版本发布说明（Release Notes）</li><li class="chapter-item expanded "><a href="../../zh/release-notes/index.html"><strong aria-hidden="true">8.</strong> 新增功能</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-5.4.html"><strong aria-hidden="true">8.1.</strong> TypeScript 5.4</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-5.3.html"><strong aria-hidden="true">8.2.</strong> TypeScript 5.3</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-5.2.html" class="active"><strong aria-hidden="true">8.3.</strong> TypeScript 5.2</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-5.1.html"><strong aria-hidden="true">8.4.</strong> TypeScript 5.1</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-5.0.html"><strong aria-hidden="true">8.5.</strong> TypeScript 5.0</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-4.9.html"><strong aria-hidden="true">8.6.</strong> TypeScript 4.9</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-4.8.html"><strong aria-hidden="true">8.7.</strong> TypeScript 4.8</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-4.7.html"><strong aria-hidden="true">8.8.</strong> TypeScript 4.7</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-4.6.html"><strong aria-hidden="true">8.9.</strong> TypeScript 4.6</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-4.5.html"><strong aria-hidden="true">8.10.</strong> TypeScript 4.5</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-4.4.html"><strong aria-hidden="true">8.11.</strong> TypeScript 4.4</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-4.3.html"><strong aria-hidden="true">8.12.</strong> TypeScript 4.3</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-4.2.html"><strong aria-hidden="true">8.13.</strong> TypeScript 4.2</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-4.1.html"><strong aria-hidden="true">8.14.</strong> TypeScript 4.1</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-4.0.html"><strong aria-hidden="true">8.15.</strong> TypeScript 4.0</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-3.9.html"><strong aria-hidden="true">8.16.</strong> TypeScript 3.9</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-3.8.html"><strong aria-hidden="true">8.17.</strong> TypeScript 3.8</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-3.7.html"><strong aria-hidden="true">8.18.</strong> TypeScript 3.7</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-3.6.html"><strong aria-hidden="true">8.19.</strong> TypeScript 3.6</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-3.5.html"><strong aria-hidden="true">8.20.</strong> TypeScript 3.5</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-3.4.html"><strong aria-hidden="true">8.21.</strong> TypeScript 3.4</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-3.3.html"><strong aria-hidden="true">8.22.</strong> TypeScript 3.3</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-3.2.html"><strong aria-hidden="true">8.23.</strong> TypeScript 3.2</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-3.1.html"><strong aria-hidden="true">8.24.</strong> TypeScript 3.1</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-3.0.html"><strong aria-hidden="true">8.25.</strong> TypeScript 3.0</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-2.9.html"><strong aria-hidden="true">8.26.</strong> TypeScript 2.9</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-2.8.html"><strong aria-hidden="true">8.27.</strong> TypeScript 2.8</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-2.7.html"><strong aria-hidden="true">8.28.</strong> TypeScript 2.7</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-2.6.html"><strong aria-hidden="true">8.29.</strong> TypeScript 2.6</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-2.5.html"><strong aria-hidden="true">8.30.</strong> TypeScript 2.5</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-2.4.html"><strong aria-hidden="true">8.31.</strong> TypeScript 2.4</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-2.3.html"><strong aria-hidden="true">8.32.</strong> TypeScript 2.3</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-2.2.html"><strong aria-hidden="true">8.33.</strong> TypeScript 2.2</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-2.1.html"><strong aria-hidden="true">8.34.</strong> TypeScript 2.1</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-2.0.html"><strong aria-hidden="true">8.35.</strong> TypeScript 2.0</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-1.8.html"><strong aria-hidden="true">8.36.</strong> TypeScript 1.8</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-1.7.html"><strong aria-hidden="true">8.37.</strong> TypeScript 1.7</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-1.6.html"><strong aria-hidden="true">8.38.</strong> TypeScript 1.6</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-1.5.html"><strong aria-hidden="true">8.39.</strong> TypeScript 1.5</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-1.4.html"><strong aria-hidden="true">8.40.</strong> TypeScript 1.4</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-1.3.html"><strong aria-hidden="true">8.41.</strong> TypeScript 1.3</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-1.1.html"><strong aria-hidden="true">8.42.</strong> TypeScript 1.1</a></li></ol></li><li class="chapter-item expanded "><li class="part-title">破坏性改动（Breaking Changes）</li><li class="chapter-item expanded "><a href="../../zh/breaking-changes/index.html"><strong aria-hidden="true">9.</strong> Breaking Changes</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../zh/breaking-changes/typescript-3.6.html"><strong aria-hidden="true">9.1.</strong> TypeScript 3.6</a></li><li class="chapter-item expanded "><a href="../../zh/breaking-changes/typescript-3.5.html"><strong aria-hidden="true">9.2.</strong> TypeScript 3.5</a></li><li class="chapter-item expanded "><a href="../../zh/breaking-changes/typescript-3.4.html"><strong aria-hidden="true">9.3.</strong> TypeScript 3.4</a></li><li class="chapter-item expanded "><a href="../../zh/breaking-changes/typescript-3.2.html"><strong aria-hidden="true">9.4.</strong> TypeScript 3.2</a></li><li class="chapter-item expanded "><a href="../../zh/breaking-changes/typescript-3.1.html"><strong aria-hidden="true">9.5.</strong> TypeScript 3.1</a></li><li class="chapter-item expanded "><a href="../../zh/breaking-changes/typescript-3.0.html"><strong aria-hidden="true">9.6.</strong> TypeScript 3.0</a></li><li class="chapter-item expanded "><a href="../../zh/breaking-changes/typescript-2.9.html"><strong aria-hidden="true">9.7.</strong> TypeScript 2.9</a></li><li class="chapter-item expanded "><a href="../../zh/breaking-changes/typescript-2.8.html"><strong aria-hidden="true">9.8.</strong> TypeScript 2.8</a></li><li class="chapter-item expanded "><a href="../../zh/breaking-changes/typescript-2.7.html"><strong aria-hidden="true">9.9.</strong> TypeScript 2.7</a></li><li class="chapter-item expanded "><a href="../../zh/breaking-changes/typescript-2.6.html"><strong aria-hidden="true">9.10.</strong> TypeScript 2.6</a></li><li class="chapter-item expanded "><a href="../../zh/breaking-changes/typescript-2.4.html"><strong aria-hidden="true">9.11.</strong> TypeScript 2.4</a></li><li class="chapter-item expanded "><a href="../../zh/breaking-changes/typescript-2.3.html"><strong aria-hidden="true">9.12.</strong> TypeScript 2.3</a></li><li class="chapter-item expanded "><a href="../../zh/breaking-changes/typescript-2.2.html"><strong aria-hidden="true">9.13.</strong> TypeScript 2.2</a></li><li class="chapter-item expanded "><a href="../../zh/breaking-changes/typescript-2.1.html"><strong aria-hidden="true">9.14.</strong> TypeScript 2.1</a></li><li class="chapter-item expanded "><a href="../../zh/breaking-changes/typescript-2.0.html"><strong aria-hidden="true">9.15.</strong> TypeScript 2.0</a></li><li class="chapter-item expanded "><a href="../../zh/breaking-changes/typescript-1.8.html"><strong aria-hidden="true">9.16.</strong> TypeScript 1.8</a></li><li class="chapter-item expanded "><a href="../../zh/breaking-changes/typescript-1.7.html"><strong aria-hidden="true">9.17.</strong> TypeScript 1.7</a></li><li class="chapter-item expanded "><a href="../../zh/breaking-changes/typescript-1.6.html"><strong aria-hidden="true">9.18.</strong> TypeScript 1.6</a></li><li class="chapter-item expanded "><a href="../../zh/breaking-changes/typescript-1.5.html"><strong aria-hidden="true">9.19.</strong> TypeScript 1.5</a></li><li class="chapter-item expanded "><a href="../../zh/breaking-changes/typescript-1.4.html"><strong aria-hidden="true">9.20.</strong> TypeScript 1.4</a></li></ol></li></ol>
            </div>
            <div id="sidebar-resize-handle" class="sidebar-resize-handle">
                <div class="sidebar-resize-indicator"></div>
            </div>
        </nav>

        <!-- Track and set sidebar scroll position -->
        <script>
            var sidebarScrollbox = document.querySelector('#sidebar .sidebar-scrollbox');
            sidebarScrollbox.addEventListener('click', function(e) {
                if (e.target.tagName === 'A') {
                    sessionStorage.setItem('sidebar-scroll', sidebarScrollbox.scrollTop);
                }
            }, { passive: true });
            var sidebarScrollTop = sessionStorage.getItem('sidebar-scroll');
            sessionStorage.removeItem('sidebar-scroll');
            if (sidebarScrollTop) {
                // preserve sidebar scroll position when navigating via links within sidebar
                sidebarScrollbox.scrollTop = sidebarScrollTop;
            } else {
                // scroll sidebar to current active section when navigating via "next/previous chapter" buttons
                var activeSection = document.querySelector('#sidebar .active');
                if (activeSection) {
                    activeSection.scrollIntoView({ block: 'center' });
                }
            }
        </script>

        <div id="page-wrapper" class="page-wrapper">

            <div class="page">
                                <div id="menu-bar-hover-placeholder"></div>
                <div id="menu-bar" class="menu-bar sticky">
                    <div class="left-buttons">
                        <label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
                            <i class="fa fa-bars"></i>
                        </label>
                        <button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
                            <i class="fa fa-paint-brush"></i>
                        </button>
                        <ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
                            <li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
                        </ul>
                        <button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
                            <i class="fa fa-search"></i>
                        </button>
                    </div>

                    <h1 class="menu-title">TypeScript 使用指南手册</h1>

                    <div class="right-buttons">
                        <a href="../../print.html" title="Print this book" aria-label="Print this book">
                            <i id="print-button" class="fa fa-print"></i>
                        </a>

                    </div>
                </div>

                <div id="search-wrapper" class="hidden">
                    <form id="searchbar-outer" class="searchbar-outer">
                        <input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
                    </form>
                    <div id="searchresults-outer" class="searchresults-outer hidden">
                        <div id="searchresults-header" class="searchresults-header"></div>
                        <ul id="searchresults">
                        </ul>
                    </div>
                </div>

                <!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
                <script>
                    document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
                    document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
                    Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
                        link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
                    });
                </script>

                <div id="content" class="content">
                    <main>
                        <h1 id="typescript-52"><a class="header" href="#typescript-52">TypeScript 5.2</a></h1>
<h2 id="using-声明与显式资源管理"><a class="header" href="#using-声明与显式资源管理"><code>using</code> 声明与显式资源管理</a></h2>
<p>TypeScript 5.2 支持了 ECMAScript 即将引入的新功能 <a href="https://github.com/tc39/proposal-explicit-resource-management">显式资源管理</a>。
让我们探索一下引入该功能的一些动机，并理解这个功能给我们带来了什么。</p>
<p>在创建对象之后需要进行某种形式的“清理”是很常见的。例如，您可能需要关闭网络连接，删除临时文件，或者只是释放一些内存。
让我们来想象一个函数，它创建一个临时文件，对它进行多种操作的读写，然后关闭并删除它。</p>
<pre><code class="language-ts">import * as fs from 'fs';

export function doSomeWork() {
  const path = '.some_temp_file';
  const file = fs.openSync(path, 'w+');

  // use file...

  // Close the file and delete it.
  fs.closeSync(file);
  fs.unlinkSync(path);
}
</code></pre>
<p>这看起来不错，但如果需要提前退出会发生什么？</p>
<pre><code class="language-ts">export function doSomeWork() {
  const path = '.some_temp_file';
  const file = fs.openSync(path, 'w+');

  // use file...
  if (someCondition()) {
    // do some more work...

    // Close the file and delete it.
    fs.closeSync(file);
    fs.unlinkSync(path);
    return;
  }

  // Close the file and delete it.
  fs.closeSync(file);
  fs.unlinkSync(path);
}
</code></pre>
<p>我们可以看到存在重复的容易忘记的清理代码。
同时无法保证在代码抛出异常时，关闭和删除文件会被执行。
解决办法是用 <code>try</code>/<code>finally</code> 语句包裹整段代码。</p>
<pre><code class="language-ts">export function doSomeWork() {
  const path = '.some_temp_file';
  const file = fs.openSync(path, 'w+');

  try {
    // use file...

    if (someCondition()) {
      // do some more work...
      return;
    }
  } finally {
    // Close the file and delete it.
    fs.closeSync(file);
    fs.unlinkSync(path);
  }
}
</code></pre>
<p>虽说这样写更加健壮，但是也为我们的代码增加了一些“噪音”。
如果我们在 <code>finally</code> 块中开始添加更多的清理逻辑，还可能遇到其他的自食其果的问题。
例如，异常可能会阻止其他资源的释放。
这些就是<a href="https://github.com/tc39/proposal-explicit-resource-management">显式资源管理</a>想要解决的问题。
该提案的关键思想是将资源释放（我们试图处理的清理工作）作为 JavaScript 中的一等概念来支持。</p>
<p>首先，增加了一个新的 <code>symbol</code> 名字为 <code>Symbol.dispose</code>，然后可以定义包含 <code>Symbol.dispose</code> 方法的对象。
为了方便，TypeScript 为此定义了一个新的全局类型 <code>Disposable</code>。</p>
<pre><code class="language-ts">class TempFile implements Disposable {
  #path: string;
  #handle: number;

  constructor(path: string) {
    this.#path = path;
    this.#handle = fs.openSync(path, 'w+');
  }

  // other methods

  [Symbol.dispose]() {
    // Close the file and delete it.
    fs.closeSync(this.#handle);
    fs.unlinkSync(this.#path);
  }
}
</code></pre>
<p>之后可以调用这些方法</p>
<pre><code class="language-ts">export function doSomeWork() {
  const file = new TempFile('.some_temp_file');

  try {
    // ...
  } finally {
    file[Symbol.dispose]();
  }
}
</code></pre>
<p>将清理逻辑移动到 <code>TempFile</code> 本身没有带来多大的价值；仅仅是将清理的代码从 <code>finally</code> 提取到方法而已，你总是可以这样做。
但如果该方法有一个众所周知的名字那么 JavaScript 就可以基于此构造其它功能。</p>
<p>这将引出该功能的第一个亮点：<code>using</code> 声明！
<code>using</code> 是一个新的关键字，支持声明新的不可变绑定，像 <code>const</code> 一样。
不同点是 <code>using</code> 声明的变量在即将离开其作用域时，它的 <code>Symbol.dispose</code> 方法会被调用！</p>
<p>因此，我们可以这样编写代码：</p>
<pre><code class="language-ts">export function doSomeWork() {
    using file = new TempFile(".some_temp_file");

    // use file...

    if (someCondition()) {
        // do some more work...
        return;
    }
}
</code></pre>
<p>看一下 - 没有 <code>try</code> / <code>finally</code> 代码块！至少，我们没有见到。
从功能上讲，这些正是 <code>using</code> 声明要帮我们做的事，但我们不必自己处理它。</p>
<p>你可能熟悉 C# 中的 <code>using</code>， Python 中的 <code>with</code>，Java 中的 <code>try-with-resource</code> 声明。
这些与 JavaScript 中的 <code>using</code> 关键字是相似的，都提供了一种明确的方式来“清理”对象，在它们即将离开作用域时。</p>
<p><code>using</code> 声明在其所在的作用域的最后才执行清理工作，或在“提前返回”（如 <code>return</code> 语句或 <code>throw</code> 错误）之前执行清理工作。
释放的顺序是先入后出，像栈一样。</p>
<pre><code class="language-ts">function loggy(id: string): Disposable {
    console.log(`Creating ${id}`);

    return {
        [Symbol.dispose]() {
            console.log(`Disposing ${id}`);
        }
    }
}

function func() {
    using a = loggy("a");
    using b = loggy("b");
    {
        using c = loggy("c");
        using d = loggy("d");
    }
    using e = loggy("e");
    return;

    // Unreachable.
    // Never created, never disposed.
    using f = loggy("f");
}

func();
// Creating a
// Creating b
// Creating c
// Creating d
// Disposing d
// Disposing c
// Creating e
// Disposing e
// Disposing b
// Disposing a
</code></pre>
<p><code>using</code> 声明对异常具有适应性；如果抛出了一个错误，那么在资源释放后会重新抛出错误。
另一方面，一个函数体可能正常执行，但是 <code>Symbol.dispose</code> 可能抛出异常。
这种情况下，异常会被重新抛出。</p>
<p>但如果释放之前的逻辑以及释放时的逻辑都抛出了异常会发生什么？
为处理这类情况引入了一个新的类型 <code>SuppressedError</code>，它是 <code>Error</code> 类型的子类型。
<code>SuppressedError</code> 类型的 <code>suppressed</code> 属性保存了上一个错误，同时 <code>error</code> 属性保存了最后抛出的错误。</p>
<pre><code class="language-ts">class ErrorA extends Error {
    name = "ErrorA";
}
class ErrorB extends Error {
    name = "ErrorB";
}

function throwy(id: string) {
    return {
        [Symbol.dispose]() {
            throw new ErrorA(`Error from ${id}`);
        }
    };
}

function func() {
    using a = throwy("a");
    throw new ErrorB("oops!")
}

try {
    func();
}
catch (e: any) {
    console.log(e.name); // SuppressedError
    console.log(e.message); // An error was suppressed during disposal.

    console.log(e.error.name); // ErrorA
    console.log(e.error.message); // Error from a

    console.log(e.suppressed.name); // ErrorB
    console.log(e.suppressed.message); // oops!
}
</code></pre>
<p>你可能已经注意到了，在这些例子中使用的都是同步方法。
然而，很多资源释放的场景涉及到<em>异步</em>操作，我们需要等待它们完成才能进行后续的操作。</p>
<p>这就是为什么现在还有一个新的 <code>Symbol.asyncDispose</code>，它带来了另一个亮点 -
<code>await using</code> 声明。
它与 <code>using</code> 声明相似，但关键是它查找需要 <code>await</code> 的资源。
它使用名为 <code>Symbol.asyncDispose</code> 的方法，尽管它们也可以操作在任何具有 <code>Symbol.dispose</code> 的对象上操作。
为了方便，TypeScript 引入了全局类型 <code>AsyncDisposable</code> 用来表示拥有异步 <code>dispose</code> 方法的对象。</p>
<pre><code class="language-ts">async function doWork() {
    // Do fake work for half a second.
    await new Promise(resolve =&gt; setTimeout(resolve, 500));
}

function loggy(id: string): AsyncDisposable {
    console.log(`Constructing ${id}`);
    return {
        async [Symbol.asyncDispose]() {
            console.log(`Disposing (async) ${id}`);
            await doWork();
        },
    }
}

async function func() {
    await using a = loggy("a");
    await using b = loggy("b");
    {
        await using c = loggy("c");
        await using d = loggy("d");
    }
    await using e = loggy("e");
    return;

    // Unreachable.
    // Never created, never disposed.
    await using f = loggy("f");
}

func();
// Constructing a
// Constructing b
// Constructing c
// Constructing d
// Disposing (async) d
// Disposing (async) c
// Constructing e
// Disposing (async) e
// Disposing (async) b
// Disposing (async) a
</code></pre>
<p>如果你期望其他人能够一致地执行清理逻辑，通过使用 <code>Disposable</code> 和 <code>AsyncDisposable</code> 来定义类型可以使你的代码更易于使用。
实际上，存在许多现有的类型，它们拥有 <code>dispose()</code> 或 <code>close()</code> 方法。
例如，Visual Studio Code APIs 定义了 <a href="https://code.visualstudio.com/api/references/vscode-api#Disposable">自己的 <code>Disposable</code> 接口</a>。
在浏览器和诸如 Node.js、Deno 和 Bun 等运行时中，API 也可以选择对已经具有清理方法（如文件句柄、连接等）的对象使用 <code>Symbol.dispose</code> 和 <code>Symbol.asyncDispose</code>。</p>
<p>现在也许对于库来说这听起来很不错，但对于你的场景来说可能有些过于复杂。如果你需要进行大量的临时清理，创建一个新类型可能会引入过度抽象和关于最佳实践的问题。
例如，再次以我们的 <code>TempFile</code> 示例为例。</p>
<pre><code class="language-ts">class TempFile implements Disposable {
    #path: string;
    #handle: number;

    constructor(path: string) {
        this.#path = path;
        this.#handle = fs.openSync(path, "w+");
    }

    // other methods

    [Symbol.dispose]() {
        // Close the file and delete it.
        fs.closeSync(this.#handle);
        fs.unlinkSync(this.#path);
    }
}

export function doSomeWork() {
    using file = new TempFile(".some_temp_file");

    // use file...

    if (someCondition()) {
        // do some more work...
        return;
    }
}
</code></pre>
<p>我们只是想记住调用两个函数，但这是最好的写法吗？
我们应该在构造函数中调用 <code>openSync</code>，创建一个 <code>open()</code> 方法，还是自己传递句柄？
我们是否应该为每个需要执行的操作公开一个方法，还是只将属性公开？</p>
<p>这就引出了这个特性的最后亮点：<code>DisposableStack</code> 和 <code>AsyncDisposableStack</code>。
这些对象非常适用于一次性的清理工作，以及任意数量的清理工作。
<code>DisposableStack</code> 是一个对象，它具有多个方法用于跟踪 <code>Disposable</code> 对象，并且可以接受函数来执行任意的清理工作。
我们还可以将它们分配给 <code>using</code> 变量，因为它们也是 <code>Disposable</code>！所以下面是我们可以编写原始示例的方式。</p>
<pre><code class="language-ts">function doSomeWork() {
    const path = ".some_temp_file";
    const file = fs.openSync(path, "w+");

    using cleanup = new DisposableStack();
    cleanup.defer(() =&gt; {
        fs.closeSync(file);
        fs.unlinkSync(path);
    });

    // use file...

    if (someCondition()) {
        // do some more work...
        return;
    }

    // ...
}
</code></pre>
<p>在这里，<code>defer()</code> 方法只需要一个回调函数，该回调函数将在 <code>cleanup</code> 释放后运行。
通常，在创建资源后应立即调用 <code>defer</code>（以及其他 <code>DisposableStack</code> 方法，如 <code>use</code> 和 <code>adopt</code>）。
顾名思义，<code>DisposableStack</code> 以类似堆栈的方式处理它所跟踪的所有内容，按照先进后出的顺序进行处理，因此在创建值后立即进行 <code>defer</code> 处理有助于避免奇怪的依赖问题。
<code>AsyncDisposableStack</code> 的工作原理类似，但可以跟踪异步函数和 <code>AsyncDisposable</code>，并且本身也是 <code>AsyncDisposable</code>。</p>
<p>在许多方面，<code>defer</code> 方法与 Go、Swift、Zig、Odin 等语言中的 <code>defer</code> 关键字类似，因此其使用约定应该相似。</p>
<p>由于这个特性非常新，大多数运行时环境不会原生支持它。要使用它，您需要为以下内容提供运行时的 polyfills：</p>
<ul>
<li>Symbol.dispose</li>
<li>Symbol.asyncDispose</li>
<li>DisposableStack</li>
<li>AsyncDisposableStack</li>
<li>SuppressedError</li>
</ul>
<p>然而，如果您只对使用 <code>using</code> 和 <code>await using</code> 感兴趣，您只需要为内置的 <code>symbol</code> 提供 polyfill，通常以下简单的方法可适用于大多数情况：</p>
<pre><code class="language-ts">Symbol.dispose ??= Symbol('Symbol.dispose');
Symbol.asyncDispose ??= Symbol('Symbol.asyncDispose');
</code></pre>
<p>你还需要将编译 <code>target</code> 设置为 <code>es2022</code> 或以下，配置 <code>lib</code> 为 <code>"esnext"</code> 或 <code>"esnext.disposable"</code>。</p>
<pre><code class="language-ts">{
    "compilerOptions": {
        "target": "es2022",
        "lib": ["es2022", "esnext.disposable", "dom"]
    }
}
</code></pre>
<p>更多详情请参考<a href="https://github.com/microsoft/TypeScript/pull/54505">PR</a>。</p>
<h2 id="decorator-metadata"><a class="header" href="#decorator-metadata">Decorator Metadata</a></h2>
<p>TypeScript 5.2 实现了 ECMAScript 即将引入的新功能 <a href="https://github.com/tc39/proposal-decorator-metadata">Decorator Metadata</a>。</p>
<p>这个功能的关键思想是使装饰器能够轻松地在它们所使用或嵌套的任何类上创建和使用元数据。</p>
<p>在任意的装饰器函数上，现在可以访问上下文对象的 <code>metadata</code> 属性。
<code>metadata</code> 属性是一个普通的对象。
由于 JavaScript 允许我们对其任意添加属性，它可以被用作可由每个装饰器更新的字典。
或者，由于每个 <code>metadata</code> 对象对于每个被装饰的部分来讲是等同的，它可以被用作 <code>Map</code> 的键。
当类的装饰器运行时，这个对象可以通过 <code>Symbol.metadata</code> 访问。</p>
<pre><code class="language-ts">interface Context {
  name: string;
  metadata: Record;
}

function setMetadata(_target: any, context: Context) {
  context.metadata[context.name] = true;
}

class SomeClass {
  @setMetadata
  foo = 123;

  @setMetadata
  accessor bar = 'hello!';

  @setMetadata
  baz() {}
}

const ourMetadata = SomeClass[Symbol.metadata];

console.log(JSON.stringify(ourMetadata));
// { "bar": true, "baz": true, "foo": true }
</code></pre>
<p>它可以被应用在不同的场景中。
<code>Metadata</code> 信息可以附加在调试、序列化或者依赖注入的场景中。
由于每个被装饰的类都会生成 <code>metadata</code> 对象，框架可以选择用它们做为 <code>key</code> 来访问 <code>Map</code> 或 <code>WeakMap</code>，或者跟踪它的属性。</p>
<p>例如，我们想通过装饰器来跟踪哪些属性和存取器是可以通过 <code>Json.stringify</code> 序列化的：</p>
<pre><code class="language-ts">import { serialize, jsonify } from './serializer';

class Person {
  firstName: string;
  lastName: string;

  @serialize
  age: number;

  @serialize
  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }

  toJSON() {
    return jsonify(this);
  }

  constructor(firstName: string, lastName: string, age: number) {
    // ...
  }
}
</code></pre>
<p>此处的意图是，只有 <code>age</code> 和 <code>fullName</code> 可以被序列化，因为它们应用了 <code>@serialize</code> 装饰器。
我们定义了 <code>toJSON</code> 方法来做这件事，但它只是调用了 <code>jsonfy</code>，它会使用 <code>@serialize</code> 创建的 <code>metadata</code>。</p>
<p>下面是 <code>./serialize.ts</code> 可能的定义：</p>
<pre><code class="language-ts">const serializables = Symbol();

type Context =
  | ClassAccessorDecoratorContext
  | ClassGetterDecoratorContext
  | ClassFieldDecoratorContext;

export function serialize(_target: any, context: Context): void {
  if (context.static || context.private) {
    throw new Error('Can only serialize public instance members.');
  }
  if (typeof context.name === 'symbol') {
    throw new Error('Cannot serialize symbol-named properties.');
  }

  const propNames = ((context.metadata[serializables] as
    | string[]
    | undefined) ??= []);
  propNames.push(context.name);
}

export function jsonify(instance: object): string {
  const metadata = instance.constructor[Symbol.metadata];
  const propNames = metadata?.[serializables] as string[] | undefined;
  if (!propNames) {
    throw new Error('No members marked with @serialize.');
  }

  const pairStrings = propNames.map(key =&gt; {
    const strKey = JSON.stringify(key);
    const strValue = JSON.stringify((instance as any)[key]);
    return `${strKey}: ${strValue}`;
  });

  return `{ ${pairStrings.join(', ')} }`;
}
</code></pre>
<p>该方法有一个局部 <code>symbol</code> 名字为 <code>serializables</code> 用于保存和获取使用 <code>@serializable</code> 标记的属性。
当每次调用 <code>@serializable</code> 时，它都会在 <code>metadata</code> 上保存这些属性名。
当 <code>jsonfy</code> 被调用时，从 <code>metadata</code> 上获取属性列表，之后从实例上获取实际值，最后序列化名和值。</p>
<p>使用 <code>symbol</code> 意味着该数据可以被他人访问。
另一选择是使用 <code>WeakMap</code> 并用该 <code>metadata</code> 对象做为键。
这样可以保持数据的私密性，并且在这种情况下使用更少的类型断言，但其他方面类似。</p>
<pre><code class="language-ts">const serializables = new WeakMap();

type Context =
  | ClassAccessorDecoratorContext
  | ClassGetterDecoratorContext
  | ClassFieldDecoratorContext;

export function serialize(_target: any, context: Context): void {
  if (context.static || context.private) {
    throw new Error('Can only serialize public instance members.');
  }
  if (typeof context.name !== 'string') {
    throw new Error('Can only serialize string properties.');
  }

  let propNames = serializables.get(context.metadata);
  if (propNames === undefined) {
    serializables.set(context.metadata, (propNames = []));
  }
  propNames.push(context.name);
}

export function jsonify(instance: object): string {
  const metadata = instance.constructor[Symbol.metadata];
  const propNames = metadata &amp;&amp; serializables.get(metadata);
  if (!propNames) {
    throw new Error('No members marked with @serialize.');
  }
  const pairStrings = propNames.map(key =&gt; {
    const strKey = JSON.stringify(key);
    const strValue = JSON.stringify((instance as any)[key]);
    return `${strKey}: ${strValue}`;
  });

  return `{ ${pairStrings.join(', ')} }`;
}
</code></pre>
<p>注意，这里的实现没有考虑子类和继承。
留给读者作为练习。</p>
<p>由于该功能比较新，大多数运行时都没实现它。
如果想要使用，则需要使用 <code>Symbol.metadata</code> 的 <code>polyfill</code>。
例如像下面这样就可以适用大部分场景：</p>
<pre><code class="language-ts">Symbol.metadata ??= Symbol('Symbol.metadata');
</code></pre>
<p>你还需要将编译 <code>target</code> 设为 <code>es2022</code> 或以下，配置 <code>lib</code> 为 <code>"esnext"</code> 或者 <code>"esnext.decorators"</code>。</p>
<pre><code>{
    "compilerOptions": {
        "target": "es2022",
        "lib": ["es2022", "esnext.decorators", "dom"]
    }
}
</code></pre>
<p>感谢 <a href="https://github.com/a-tarasyuk">Oleksandr Tarasiuk</a>的<a href="https://github.com/microsoft/TypeScript/pull/54657">贡献</a>。</p>
<h2 id="命名的和匿名的元组元素"><a class="header" href="#命名的和匿名的元组元素">命名的和匿名的元组元素</a></h2>
<p>元组类型已经支持了为每个元素定义可选的标签和命名。</p>
<pre><code class="language-ts">type Pair = [first: T, second: T];
</code></pre>
<p>这些标签不改变功能 - 它们只是用于增强可读性和工具支持。</p>
<p>然而，TypeScript 之前有个限制是不允许混用有标签和无标签的元素。
换句话说，要么所有元素都没有标签，要么所有元素都有标签。</p>
<pre><code class="language-ts">// ✅ fine - no labels
type Pair1 = [T, T];

// ✅ fine - all fully labeled
type Pair2 = [first: T, second: T];

// ❌ previously an error
type Pair3 = [first: T, T];
//                         ~
// Tuple members must all have names
// or all not have names.
</code></pre>
<p>如果是剩余元素就比较烦人了，我们必须要添加标签 <code>rest</code> 或者 <code>tail</code>。</p>
<pre><code class="language-ts">// ❌ previously an error
type TwoOrMore_A = [first: T, second: T, ...T[]];
//                                          ~~~~~~
// Tuple members must all have names
// or all not have names.

// ✅
type TwoOrMore_B = [first: T, second: T, rest: ...T[]];
</code></pre>
<p>这也意味着这个限制必须在类型系统内部进行强制执行，这意味着 TypeScript 将失去标签。</p>
<pre><code class="language-ts">type HasLabels = [a: string, b: string];
type HasNoLabels = [number, number];
type Merged = [...HasNoLabels, ...HasLabels];
//   ^ [number, number, string, string]
//
//     'a' and 'b' were lost in 'Merged'
</code></pre>
<p>在 TypeScript 5.2 中，对元组标签的全有或全无限制已经被取消。
而且现在可以在展开的元组中保留标签。</p>
<p>感谢 <a href="https://github.com/JoshuaKGoldberg">Josh Goldberg</a> 和 <a href="https://github.com/Andarist">Mateusz Burzyński</a> 的贡献。</p>
<h2 id="更容易地使用联合数组上的方法"><a class="header" href="#更容易地使用联合数组上的方法">更容易地使用联合数组上的方法</a></h2>
<p>在之前版本的 TypeScript 中，在联合数组上调用方法可能很痛苦。</p>
<pre><code class="language-ts">declare let array: string[] | number[];

array.filter(x =&gt; !!x);
//    ~~~~~~ error!
// This expression is not callable.
//   Each member of the union type '...' has signatures,
//   but none of those signatures are compatible
//   with each other.
</code></pre>
<p>此例中，TypeScript 会检查是否每个版本的 <code>filter</code> 都与 <code>string[]</code> 和 <code>number[]</code> 兼容。
在没有一个连贯的策略的情况下，TypeScript 会束手无策地说：“我无法使其工作”。</p>
<p>在 TypeScript 5.2 里，在放弃之前，联合数组会被特殊对待。
使用每个元素类型构造一个新数组，然后在其上调用方法。</p>
<p>对于上例来说，<code>string[] | number[]</code> 被转换为 <code>(string | number)[]</code>（或者是 <code>Array&lt;string | number&gt;</code>），然后在该类型上调用 <code>filter</code>。
有一个注意事项，<code>filter</code> 会产生 <code>Array&lt;string | number&gt;</code> 而不是 <code>string[] | number[]</code>；
但对于新产生的值，出现“出错”的风险较小。</p>
<p>这意味着在以前不能使用的情况下，许多方法如 <code>filter</code>、<code>find</code>、<code>some</code>、<code>every</code> 和 <code>reduce</code> 都可以在数组的联合类型上调用。</p>
<p>更多详情请参考<a href="https://github.com/microsoft/TypeScript/pull/53489">PR</a>。</p>
<h2 id="拷贝的数组方法"><a class="header" href="#拷贝的数组方法">拷贝的数组方法</a></h2>
<p>TypeScript 5.2 支持了 ECMAScript 提案 <a href="https://github.com/tc39/proposal-change-array-by-copy">Change Array by Copy</a>。</p>
<p>JavaScript 中的数组有很多有用的方法如 <code>sort()</code>，<code>splice()</code>，以及 <code>reverse()</code>，这些方法在数组中原地修改元素。
通常，我们想创建一个新数组，还想影响原来的数组。
为达到此目的，你可以使用 <code>slice()</code> 或者展开数组（例如 <code>[...myArray]</code>）获取一份拷贝，然后再执行操作。
例如，你可以用 <code>myArray.slice().reverse()</code> 来获取反转的数组的拷贝。</p>
<p>还有一个典型的例子 - 创建一份拷贝，但是修改其中的一个元素。
有许多方法可以实现这一点，但最明显的方法要么是由多个语句组成的...</p>
<pre><code class="language-ts">const copy = myArray.slice();
copy[someIndex] = updatedValue;
doSomething(copy);
</code></pre>
<p>要么意图不明显...</p>
<pre><code class="language-ts">doSomething(
  myArray.map((value, index) =&gt; (index === someIndex ? updatedValue : value))
);
</code></pre>
<p>所有这些对于如此常见的操作来说都很繁琐。
这就是为什么 JavaScript 现在有了 4 个新的方法，执行相同的操作，但不影响原始数据：<code>toSorted</code>、<code>toSpliced</code>、<code>toReversed</code> 和 <code>with</code>。
前三个方法执行与它们的变异版本相同的操作，但返回一个新的数组。
<code>with</code> 也返回一个新的数组，但其中一个元素被更新（如上所述）。</p>
<div class="table-wrapper"><table><thead><tr><th>修改</th><th>拷贝</th></tr></thead><tbody>
<tr><td>myArray.reverse()</td><td>myArray.toReversed()</td></tr>
<tr><td>myArray.sort((a, b) =&gt; ...)</td><td>myArray.toSorted((a, b) =&gt; ...)</td></tr>
<tr><td>myArray.splice(start, deleteCount, ...items)</td><td>myArray.toSpliced(start, deleteCount, ...items)</td></tr>
<tr><td>myArray[index] = updatedValue</td><td>myArray.with(index, updatedValue)</td></tr>
</tbody></table>
</div>
<p>请注意，复制方法始终创建一个新的数组，而修改操作则不一致。</p>
<p>这些方法不仅存在于普通数组上 - 它们还存在于 <code>TypedArray</code> 上，例如 <code>Int32Array</code>，<code>Uint8Array</code>，等。</p>
<p>感谢 <a href="https://github.com/sno2">Carter Snook</a> 的 <a href="https://github.com/microsoft/TypeScript/pull/51367">PR</a>。</p>
<h2 id="将-symbol-用于-weakmap-和-weakset-的键"><a class="header" href="#将-symbol-用于-weakmap-和-weakset-的键">将 <code>symbol</code> 用于 <code>WeakMap</code> 和 <code>WeakSet</code> 的键</a></h2>
<p>现在可以将 <code>symbol</code> 用于 <code>WeakMap</code> 和 <code>WeakSet</code> 的键，它也是 ECMAScript 的<a href="https://github.com/tc39/proposal-symbols-as-weakmap-keys">新功能</a>。</p>
<pre><code class="language-ts">const myWeakMap = new WeakMap();

const key = Symbol();
const someObject = { /*...*/ };

// Works! ✅
myWeakMap.set(key, someObject);
myWeakMap.has(key);
</code></pre>
<p><a href="https://github.com/microsoft/TypeScript/pull/54195">这个更新</a>是由 <a href="https://github.com/leoelm">Leo Elmecker-Plakolm</a> 代表 Bloomberg 提供的。我们想向他们表示感谢！</p>
<h2 id="类型导入路径里使用-typescript-实现文件扩展名"><a class="header" href="#类型导入路径里使用-typescript-实现文件扩展名">类型导入路径里使用 TypeScript 实现文件扩展名</a></h2>
<p>TypeScript 支持在类型导入路径里使用声明文件扩展名和实现文件扩展名，不论是否启用了 <code>allowImportingTsExtensions</code>。</p>
<p>也意味着你现在可以编写 <code>import type</code> 语句并使用 <code>.ts</code>, <code>.mts</code>, <code>.cts</code> 以及 <code>.tsx</code> 文件扩展。</p>
<pre><code class="language-ts">import type { JustAType } from "./justTypes.ts";

export function f(param: JustAType) {
    // ...
}
</code></pre>
<p>这也意味着，<code>import()</code> 类型（用在 TypeScript 和 JavaScript 的 JSDoc 中） 也可以使用这些扩展名。</p>
<pre><code class="language-ts">/**
 * @param {import("./justTypes.ts").JustAType} param
 */
export function f(param) {
    // ...
}
</code></pre>
<p>更多详情请查看 <a href="https://github.com/microsoft/TypeScript/pull/54746">PR</a>。</p>
<h2 id="对象成员的逗号补全"><a class="header" href="#对象成员的逗号补全">对象成员的逗号补全</a></h2>
<p>在给对象添加新属性时很容易忘记添加逗号。
在之前，如果你忘了写逗号并且请求自动补全，TypeScript 会给出差的不相关的补全结果。</p>
<p>TypeScript 5.2 现在在您缺少逗号时会优雅地提供对象成员的自动补全。
但为了避免语法错误的出现，它还会自动插入缺失的逗号。</p>
<p>更多详情请查看 <a href="https://github.com/microsoft/TypeScript/pull/52899">PR</a>。</p>
<h2 id="内联变量重构"><a class="header" href="#内联变量重构">内联变量重构</a></h2>
<p>TypeScript 5.2 现在具有一种重构方法，可以将变量的内容内联到所有使用位置。</p>
<p>使用“内联变量”重构将消除变量并将所有变量的使用替换为其初始化值。
请注意，这可能会导致初始化程序的副作用在不同的时间运行，并且运行的次数与变量的使用次数相同。</p>
<p>更多详情请查看 <a href="https://github.com/microsoft/TypeScript/pull/54281">PR</a>。</p>
<h2 id="可点击的内嵌参数提示"><a class="header" href="#可点击的内嵌参数提示">可点击的内嵌参数提示</a></h2>
<p>内嵌提示可以让我们一目了然地获取信息，即使它在我们的代码中不存在 —— 比如参数名称、推断类型等等。
在 TypeScript 5.2 中，我们开始使得与内嵌提示进行交互成为可能。
例如，在 Visual Studio Code Insiders 中，您现在可以点击内联提示以跳转到参数的定义处。</p>
<p>更多详情请查看 <a href="https://github.com/microsoft/TypeScript/pull/54734">PR</a>。</p>
<h2 id="优化进行中的类型兼容性检查"><a class="header" href="#优化进行中的类型兼容性检查">优化进行中的类型兼容性检查</a></h2>
<p>由于 TypeScript 采用的是结构化的类型系统，通常需要比较类型成员；
然而，递归类型会造成一些问题。例如：</p>
<pre><code class="language-ts">interface A {
    value: A;
    other: string;
}

interface B {
    value: B;
    other: number;
}
</code></pre>
<p>在检查 <code>A</code> 是否与 <code>B</code> 类型兼容时，TypeScript 会检查 <code>A</code> 和 <code>B</code> 中 <code>value</code> 的类型是否兼容。
此时，类型系统需要停止进一步检查并继续检查其他成员。
为此，类型系统必须跟踪两个类型是否已经相关联。</p>
<p>此前，TypeScript 已经保存了配对类型的栈，并迭代检查类型是否已经关联。
当这个堆栈很浅时，这不是一个问题；但当堆栈不是很浅时，那就是个<a href="https://accidentallyquadratic.tumblr.com/">问题</a>了。</p>
<p>在 TypeScript 5.2 中，一个简单的 <code>Set</code> 就能跟踪这些信息。
在使用了 drizzle 库的测试报告中，这项改动减少了超过 33% 的时间花费！</p>
<pre><code>Benchmark 1: old
  Time (mean ± σ):      3.115 s ±  0.067 s    [User: 4.403 s, System: 0.124 s]
  Range (min … max):    3.018 s …  3.196 s    10 runs

Benchmark 2: new
  Time (mean ± σ):      2.072 s ±  0.050 s    [User: 3.355 s, System: 0.135 s]
  Range (min … max):    1.985 s …  2.150 s    10 runs

Summary
  'new' ran
    1.50 ± 0.05 times faster than 'old'
</code></pre>
<p>更多详情请查看 <a href="https://github.com/microsoft/TypeScript/pull/55224">PR</a>。</p>

                    </main>

                    <nav class="nav-wrapper" aria-label="Page navigation">
                        <!-- Mobile navigation buttons -->
                            <a rel="prev" href="../../zh/release-notes/typescript-5.3.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
                                <i class="fa fa-angle-left"></i>
                            </a>

                            <a rel="next prefetch" href="../../zh/release-notes/typescript-5.1.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
                                <i class="fa fa-angle-right"></i>
                            </a>

                        <div style="clear: both"></div>
                    </nav>
                </div>
            </div>

            <nav class="nav-wide-wrapper" aria-label="Page navigation">
                    <a rel="prev" href="../../zh/release-notes/typescript-5.3.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
                        <i class="fa fa-angle-left"></i>
                    </a>

                    <a rel="next prefetch" href="../../zh/release-notes/typescript-5.1.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
                        <i class="fa fa-angle-right"></i>
                    </a>
            </nav>

        </div>




        <script>
            window.playground_copyable = true;
        </script>


        <script src="../../elasticlunr.min.js"></script>
        <script src="../../mark.min.js"></script>
        <script src="../../searcher.js"></script>

        <script src="../../clipboard.min.js"></script>
        <script src="../../highlight.js"></script>
        <script src="../../book.js"></script>

        <!-- Custom JS scripts -->


    </div>
    </body>
</html>
